package net.csdn.business.gateway.component;

import lombok.extern.slf4j.Slf4j;
import net.csdn.business.common.constant.RedisConstants;
import net.csdn.business.common.domain.request.oauth.OauthAuthPermissionQuery;
import net.csdn.business.common.domain.vo.oauth.OauthAuthPermissionVO;
import net.csdn.business.common.oauth2.service.IOauthAuthPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StopWatch;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Set;

/**
 * 自定义的鉴权服务，通过鉴权的才能继续访问某个请求<br>
 * 由于SpringGateWay基于WebFlux，所以SpringSecruity很多原有写法，都得改为WebFlux的方式才能生效！
 */
@Slf4j
@Component
public class CustomRBACServiceWebFlux implements ReactiveAuthorizationManager<AuthorizationContext> {

    private AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private IOauthAuthPermissionService oauthAuthPermissionService;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
        ServerHttpRequest request = object.getExchange().getRequest();
        String requestUrl = request.getPath().pathWithinApplication().value();
        log.info("请求地址为[{}]", requestUrl);
        String requestMethod =  request.getMethod().toString();
        log.info("请求方式为[{}]", requestMethod);
        return authentication
                .filter(a -> a.isAuthenticated()).map(au->{
                    if(au instanceof OAuth2Authentication){
                        //增加判断逻辑
                        OAuth2Authentication oAuth2Authentication=(OAuth2Authentication)au;
                        OAuth2Request oAuth2Request=oAuth2Authentication.getOAuth2Request();
                        Set<String> scopes=oAuth2Request.getScope();
                        // 根据路径查询路径需要什么权限才能访问
                        String[] permissions = findByPath(requestUrl,requestMethod);
                        if(scopes==null||scopes.size()==0||permissions.length==0){
                           return false;
                        }
                        for (String needRole:permissions) {
                            if(scopes.contains(needRole)){
                                return true;
                            }
                        }
                        return false;
                    }else{
                        //增加判断逻辑
                        return false;
                    }
                }).map( hasAuthority -> new AuthorizationDecision(hasAuthority))
                .defaultIfEmpty(new AuthorizationDecision(false));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {
        return check(authentication, object)
                .filter(d -> d.isGranted())
                .switchIfEmpty(Mono.defer(() -> {
                     String body = "当前用户没有访问权限!";
                    return Mono.error(new AccessDeniedException(body));
                }))
                .flatMap(d -> Mono.empty());
    }


    /***
     * 该方法用于模拟获取访问指定值地址需要的权限
     * 真实情况下要去数据库中查询，这样便于修改
     * @param requestUrl 请求的地址
     */
    private String[] findByPath(String requestUrl,String method) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("oauth.findByPath");
        //查询数据库，然后反序列化
        String[] result=null;
        List<OauthAuthPermissionVO> list=null;
        String tokenParam=method;
        Cache cache =null;
        try{
            cache = cacheManager.getCache(RedisConstants.OAUTH2_PERMMISSION);
            list=cache.get(tokenParam, List.class);
        }catch (Exception e){
            log.info("cache query key:{} error: {} !",tokenParam,e.getMessage());
        }
        if(list== null){
            OauthAuthPermissionQuery query=new OauthAuthPermissionQuery();
            query.setReqMethod(method);
            list=oauthAuthPermissionService.selectOauthRoleList(query);
            try{
                cache.put(tokenParam,list);
            }catch (Exception e){
              log.info("cache put key:{} error: {} !",tokenParam,e.getMessage());
            }
        }
        if(list==null||list.size()==0){
           return result;
        }
        result=list.stream().filter(oauthAuthPermissionVO->{
            String reqUrl=oauthAuthPermissionVO.getReqUrl();
            if (ANT_PATH_MATCHER.match(reqUrl, requestUrl)) {
                return true;
            }
            return false;
        }).map(oauthAuthPermissionVO->oauthAuthPermissionVO.getAuthRole()
        ).toArray(String[]::new);
        stopWatch.stop();
        log.info("当前任务名：{},耗时：{}ms",stopWatch.getLastTaskName(),stopWatch.getLastTaskTimeMillis());
        return result;
    }
}
